import tkinter as tk
from tkinter import messagebox
import random
import time
from abc import ABC, abstractmethod
import copy

class OthelloGame:
    """黑白棋游戏逻辑，完全独立于UI和玩家"""
    def __init__(self):
        # 游戏状态常量
        self.EMPTY = 0
        self.BLACK = 1
        self.WHITE = 2
        self.BOARD_SIZE = 8
        
        # 初始化游戏
        self.reset_game()
        
    def reset_game(self):
        """重置游戏状态"""
        # 初始化棋盘
        self.board = [[self.EMPTY for _ in range(self.BOARD_SIZE)] for _ in range(self.BOARD_SIZE)]
        
        # 设置初始棋子
        self.board[3][3] = self.WHITE
        self.board[3][4] = self.BLACK
        self.board[4][3] = self.BLACK
        self.board[4][4] = self.WHITE
        
        # 当前玩家 (黑棋先行)
        self.current_player = self.BLACK
        
        # 游戏状态
        self.game_over = False
        
    def is_valid_move(self, row, col, player=None):
        """检查在指定位置落子是否合法"""
        if player is None:
            player = self.current_player
            
        # 如果位置不在棋盘内或已有棋子，不合法
        if row < 0 or row >= self.BOARD_SIZE or col < 0 or col >= self.BOARD_SIZE or self.board[row][col] != self.EMPTY:
            return False
            
        opponent = self.BLACK if player == self.WHITE else self.WHITE
        
        # 检查8个方向
        directions = [(-1, -1), (-1, 0), (-1, 1),
                     (0, -1),           (0, 1),
                     (1, -1),  (1, 0),  (1, 1)]
        
        for dr, dc in directions:
            r, c = row + dr, col + dc
            found_opponent = False
            
            # 沿着方向检查
            while 0 <= r < self.BOARD_SIZE and 0 <= c < self.BOARD_SIZE and self.board[r][c] == opponent:
                r += dr
                c += dc
                found_opponent = True
                
            # 如果在这个方向上有对手棋子且以己方棋子结尾，则是合法移动
            if found_opponent and 0 <= r < self.BOARD_SIZE and 0 <= c < self.BOARD_SIZE and self.board[r][c] == player:
                return True
                
        return False
        
    def get_valid_moves(self, player=None):
        """获取当前玩家的所有合法移动位置"""
        if player is None:
            player = self.current_player
            
        valid_moves = []
        for row in range(self.BOARD_SIZE):
            for col in range(self.BOARD_SIZE):
                if self.is_valid_move(row, col, player):
                    valid_moves.append((row, col))
        return valid_moves
        
    def make_move(self, row, col, player=None):
        """在指定位置落子"""
        if player is None:
            player = self.current_player
            
        if not self.is_valid_move(row, col, player):
            return False
            
        # 放置棋子
        self.board[row][col] = player
        
        opponent = self.BLACK if player == self.WHITE else self.WHITE
        
        # 翻转被夹住的对手棋子
        directions = [(-1, -1), (-1, 0), (-1, 1),
                     (0, -1),           (0, 1),
                     (1, -1),  (1, 0),  (1, 1)]
        
        for dr, dc in directions:
            r, c = row + dr, col + dc
            to_flip = []
            
            # 沿着方向检查
            while 0 <= r < self.BOARD_SIZE and 0 <= c < self.BOARD_SIZE and self.board[r][c] == opponent:
                to_flip.append((r, c))
                r += dr
                c += dc
                
            # 如果在这个方向上有对手棋子且以己方棋子结尾，则翻转这些棋子
            if 0 <= r < self.BOARD_SIZE and 0 <= c < self.BOARD_SIZE and self.board[r][c] == player:
                for flip_row, flip_col in to_flip:
                    self.board[flip_row][flip_col] = player
                    
        # 切换玩家
        self.switch_player()
        
        # 检查游戏是否结束
        if self.is_game_over():
            self.game_over = True
            
        return True
        
    def switch_player(self):
        """切换玩家"""
        next_player = self.BLACK if self.current_player == self.WHITE else self.WHITE
        
        # 如果下一个玩家没有合法移动，保持当前玩家
        if self.get_valid_moves(next_player):
            self.current_player = next_player
        
    def get_score(self):
        """获取当前比分"""
        black_count = sum(row.count(self.BLACK) for row in self.board)
        white_count = sum(row.count(self.WHITE) for row in self.board)
        return black_count, white_count
        
    def is_game_over(self):
        """检查游戏是否结束"""
        # 如果双方都没有合法移动，游戏结束
        black_moves = self.get_valid_moves(self.BLACK)
        white_moves = self.get_valid_moves(self.WHITE)
        
        if not black_moves and not white_moves:
            return True
            
        # 如果棋盘已满，游戏结束
        if all(cell != self.EMPTY for row in self.board for cell in row):
            return True
            
        return False


class Player(ABC):
    """玩家抽象基类"""
    def __init__(self, game:OthelloGame, color):
        self.game = game
        self.color = color
        
    @abstractmethod
    def get_move(self):
        """获取移动，子类必须实现此方法"""
        pass


class HumanPlayer(Player):
    """人类玩家"""
    def __init__(self, game:OthelloGame, color, ui_callback=None):
        super().__init__(game, color)
        self.ui_callback = ui_callback
        self.pending_move = None
        
    def set_ui_callback(self, callback):
        """设置UI回调，用于通知UI更新"""
        self.ui_callback = callback
        
    def get_move(self):
        """人类玩家通过UI交互获取移动"""
        # 返回等待中的移动并重置
        move = self.pending_move
        self.pending_move = None
        return move
        
    def set_move(self, row, col):
        """由UI调用，设置玩家选择的移动"""
        if self.game.current_player == self.color and self.game.is_valid_move(row, col, self.color):
            self.pending_move = (row, col)
            if self.ui_callback:
                self.ui_callback()


class AIPlayer(Player):
    """AI玩家基类"""
    def __init__(self, game:OthelloGame, color):
        super().__init__(game, color)
        self.last_move_time = 0
        
    @abstractmethod
    def find_move(self):
        """AI寻找最佳移动，子类必须实现此方法"""
        pass
        
    def get_move(self):
        """获取AI的移动，包含计时功能"""
        start_time = time.time()
        move = self.find_move()
        self.last_move_time = time.time() - start_time
        return move


class alphaBetaAI(AIPlayer):
    def __init__(self, game: OthelloGame, color, depth=6,batch=2):
        super().__init__(game, color)
        self.depth = depth
        self.batch = batch
        # self.copy_game = copy.deepcopy(game)
    
    def find_move(self):
        can_moves = self.game.get_valid_moves(self.color)
        #首先判断四角是否可占领
        corners = [(0,0), (0,7), (7,0), (7,7)]
        corners_can_use = []
        for corner in corners:
            if corner in can_moves:
                corners_can_use.append(corner)
        if corners_can_use:
            return random.choice(corners_can_use)

        if can_moves:
            best_move = [random.choice(can_moves), -float('inf')] 
        else:
            return None

        for move in can_moves:
            new_game_state = copy.deepcopy(self.game)
            new_game_state.make_move(move[0], move[1], self.color)
            move_value = self.alpha_beta(new_game_state, self.depth - 1, float('-inf'), float('inf'), False)
            if best_move is None or move_value > best_move[1]:
                best_move = (move, move_value) 

        return best_move[0]  
        
    def alpha_beta(self, game_state:OthelloGame, depth, alpha, beta, maximizing_player):
        if depth == 0 or game_state.is_game_over():
            return self.evaluate_board(game_state)
        
        valid_moves = game_state.get_valid_moves(self.color if maximizing_player else (game_state.BLACK if self.color == game_state.WHITE else game_state.WHITE))
        
        if maximizing_player:
            max_eval = float('-inf')
            for move in valid_moves:
                new_game_state = copy.deepcopy(game_state)
                new_game_state.make_move(move[0], move[1], self.color)
                eval = self.alpha_beta(new_game_state, depth - 1, alpha, beta, False)
                max_eval = max(max_eval, eval)
                alpha = max(alpha, eval)
                if beta <= alpha:
                    break
            return max_eval
        else:
            min_eval = float('inf')
            opponent_color = game_state.BLACK if self.color == game_state.WHITE else game_state.WHITE
            for move in valid_moves:
                new_game_state = copy.deepcopy(game_state)
                new_game_state.make_move(move[0], move[1], opponent_color)
                eval = self.alpha_beta(new_game_state, depth - 1, alpha, beta, True)
                min_eval = min(min_eval, eval)
                beta = min(beta, eval)
                if beta <= alpha:
                    break
            return min_eval
        
    def evaluate_board(self, game_state:OthelloGame):
        black_score, white_score = game_state.get_score()
        if self.color == game_state.BLACK:
            return black_score - white_score
        else:
            return white_score - black_score

        

class RandomAI(AIPlayer):
    """随机AI，作为占位符"""
    def find_move(self):
        """随机选择一个合法移动"""
        valid_moves = self.game.get_valid_moves(self.color)
        if valid_moves:
            return random.choice(valid_moves)
        return None


class OthelloController:
    """游戏控制器，协调游戏逻辑、玩家和UI"""
    def __init__(self, game:OthelloGame, black_player, white_player):
        self.game = game
        self.black_player = black_player
        self.white_player = white_player
        
        # 设置人类玩家的UI回调
        if isinstance(black_player, HumanPlayer):
            black_player.set_ui_callback(self.on_human_move)
        if isinstance(white_player, HumanPlayer):
            white_player.set_ui_callback(self.on_human_move)
            
        # 游戏状态
        self.ai_thinking = False
        self.ai_vs_ai_mode = False  # AI对战模式
        self.ai_delay = 500  # AI思考延迟(毫秒)
        self.move_count = 0  # 步数计数器
        
    def get_current_player(self):
        """获取当前玩家对象"""
        if self.game.current_player == self.game.BLACK:
            return self.black_player
        else:
            return self.white_player
            
    def is_current_player_ai(self):
        """检查当前玩家是否是AI"""
        return isinstance(self.get_current_player(), AIPlayer)
        
    def on_human_move(self):
        """当人类玩家做出移动时调用"""
        if self.ai_thinking or self.game.game_over:
            return
            
        player = self.get_current_player()
        if isinstance(player, HumanPlayer):
            move = player.get_move()
            if move:
                self.make_move(move[0], move[1])
                
    def make_move(self, row, col):
        """执行移动并更新游戏状态"""
        if self.game.make_move(row, col):
            self.move_count += 1  # 增加步数
            
            # 通知UI更新
            if hasattr(self, 'ui_update_callback'):
                self.ui_update_callback()
                
            # 检查游戏是否结束
            if self.game.game_over:
                if hasattr(self, 'game_over_callback'):
                    self.game_over_callback()
                return
                
            # 如果下一个玩家是AI，触发AI移动
            if self.is_current_player_ai():
                self.schedule_ai_move()
                    
    def schedule_ai_move(self):
        """安排AI移动"""
        if self.ai_thinking:
            return
            
        self.ai_thinking = True
        if hasattr(self, 'ai_start_callback'):
            self.ai_start_callback()
            
        # 延迟执行AI移动
        if hasattr(self, 'root'):
            current_move = self.move_count  # 记录当前步数
            self.root.after(self.ai_delay, lambda: self.execute_ai_move(current_move))
                    
    def execute_ai_move(self, scheduled_move):
        """执行AI移动"""
        # 检查步数是否匹配，防止多次执行
        if self.move_count != scheduled_move or self.game.game_over:
            self.ai_thinking = False
            if hasattr(self, 'ai_end_callback'):
                self.ai_end_callback()
            return
            
        player = self.get_current_player()
        if isinstance(player, AIPlayer):
            move = player.get_move()
            if move:
                self.make_move(move[0], move[1])
                
        self.ai_thinking = False
        if hasattr(self, 'ai_end_callback'):
            self.ai_end_callback()
            
        # 如果AI对战模式开启且下一个玩家是AI，继续移动
        if self.ai_vs_ai_mode and not self.game.game_over and self.is_current_player_ai():
            self.schedule_ai_move()
                
    def set_ui_callbacks(self, root, ui_update_callback, game_over_callback, 
                         ai_start_callback=None, ai_end_callback=None):
        """设置UI回调函数"""
        self.root = root
        self.ui_update_callback = ui_update_callback
        self.game_over_callback = game_over_callback
        self.ai_start_callback = ai_start_callback
        self.ai_end_callback = ai_end_callback
        
    def set_ai_vs_ai_mode(self, enabled):
        """设置AI对战模式"""
        self.ai_vs_ai_mode = enabled
        
        # 如果启用AI对战模式且当前玩家是AI，开始AI对战
        if enabled and not self.ai_thinking and self.is_current_player_ai():
            self.schedule_ai_move()
        
    def new_game(self):
        """开始新游戏"""
        self.ai_thinking = False
        self.ai_vs_ai_mode = False
        self.move_count = 0  # 重置步数
        self.game.reset_game()


class OthelloUI:
    """黑白棋用户界面"""
    def __init__(self, master, controller:OthelloController):
        self.master = master
        self.master.title("黑白棋 - AI对战框架")
        self.master.resizable(False, False)
        
        self.controller = controller
        
        # 设置UI回调
        self.controller.set_ui_callbacks(
            root=self.master,
            ui_update_callback=self.update_display,
            game_over_callback=self.on_game_over,
            ai_start_callback=self.on_ai_start,
            ai_end_callback=self.on_ai_end
        )
        
        # 创建界面元素
        self.create_widgets()
        
        # 绘制初始棋盘
        self.update_display()
        
    def create_widgets(self):
        """创建界面元素"""
        # 顶部信息栏
        self.info_frame = tk.Frame(self.master)
        self.info_frame.pack(pady=10)
        
        self.black_score_label = tk.Label(self.info_frame, text="黑棋: 2", font=("Arial", 14))
        self.black_score_label.pack(side=tk.LEFT, padx=20)
        
        self.current_player_label = tk.Label(self.info_frame, text="当前: 黑棋", font=("Arial", 14, "bold"))
        self.current_player_label.pack(side=tk.LEFT, padx=20)
        
        self.white_score_label = tk.Label(self.info_frame, text="白棋: 2", font=("Arial", 14))
        self.white_score_label.pack(side=tk.LEFT, padx=20)
        
        # 棋盘画布
        self.canvas = tk.Canvas(self.master, width=400, height=400, bg="green")
        self.canvas.pack(pady=10)
        self.canvas.bind("<Button-1>", self.canvas_click)
        
        # 控制按钮
        self.control_frame = tk.Frame(self.master)
        self.control_frame.pack(pady=10)
        
        self.new_game_btn = tk.Button(self.control_frame, text="新游戏", command=self.new_game)
        self.new_game_btn.pack(side=tk.LEFT, padx=5)
        
        self.ai_vs_ai_btn = tk.Button(self.control_frame, text="AI对战", command=self.toggle_ai_vs_ai)
        self.ai_vs_ai_btn.pack(side=tk.LEFT, padx=5)
        
        self.ai_move_btn = tk.Button(self.control_frame, text="AI走一步", command=self.ai_move)
        self.ai_move_btn.pack(side=tk.LEFT, padx=5)
        
        # 玩家类型选择
        self.player_frame = tk.Frame(self.master)
        self.player_frame.pack(pady=5)
        
        tk.Label(self.player_frame, text="黑棋:").pack(side=tk.LEFT)
        self.black_player_var = tk.StringVar(value="human")
        self.black_human_rb = tk.Radiobutton(self.player_frame, text="人类", variable=self.black_player_var, value="human", command=self.update_players)
        self.black_human_rb.pack(side=tk.LEFT)
        self.black_ai_rb = tk.Radiobutton(self.player_frame, text="AI", variable=self.black_player_var, value="ai", command=self.update_players)
        self.black_ai_rb.pack(side=tk.LEFT)
        
        tk.Label(self.player_frame, text="白棋:").pack(side=tk.LEFT, padx=(20,0))
        self.white_player_var = tk.StringVar(value="ai")
        self.white_human_rb = tk.Radiobutton(self.player_frame, text="人类", variable=self.white_player_var, value="human", command=self.update_players)
        self.white_human_rb.pack(side=tk.LEFT)
        self.white_ai_rb = tk.Radiobutton(self.player_frame, text="AI", variable=self.white_player_var, value="ai", command=self.update_players)
        self.white_ai_rb.pack(side=tk.LEFT)
        
        # 决策时间显示
        self.timer_label = tk.Label(self.master, text="决策时间: 0.0s", font=("Arial", 10))
        self.timer_label.pack(pady=5)
        
        # 状态显示
        self.status_label = tk.Label(self.master, text="", font=("Arial", 10))
        self.status_label.pack(pady=5)
        
    def update_players(self):
        """更新玩家类型"""
        # 创建新的玩家实例
        if self.black_player_var.get() == "human":
            black_player = HumanPlayer(self.controller.game, self.controller.game.BLACK)
        else:
            black_player = alphaBetaAI(self.controller.game, self.controller.game.BLACK)
            
        if self.white_player_var.get() == "human":
            white_player = HumanPlayer(self.controller.game, self.controller.game.WHITE)
        else:
            white_player = alphaBetaAI(self.controller.game, self.controller.game.WHITE)
            
        # 更新控制器中的玩家
        self.controller.black_player = black_player
        self.controller.white_player = white_player
        
        # 设置人类玩家的UI回调
        if isinstance(black_player, HumanPlayer):
            black_player.set_ui_callback(self.controller.on_human_move)
        if isinstance(white_player, HumanPlayer):
            white_player.set_ui_callback(self.controller.on_human_move)
            
        self.update_display()
        
    def update_display(self):
        """更新棋盘显示"""
        self.canvas.delete("all")
        
        cell_size = 50
        game = self.controller.game
        
        # 绘制棋盘网格
        for i in range(game.BOARD_SIZE + 1):
            # 横线
            self.canvas.create_line(0, i * cell_size, game.BOARD_SIZE * cell_size, i * cell_size)
            # 竖线
            self.canvas.create_line(i * cell_size, 0, i * cell_size, game.BOARD_SIZE * cell_size)
        
        # 绘制棋子
        for row in range(game.BOARD_SIZE):
            for col in range(game.BOARD_SIZE):
                x = col * cell_size + cell_size // 2
                y = row * cell_size + cell_size // 2
                
                if game.board[row][col] == game.BLACK:
                    self.canvas.create_oval(x-20, y-20, x+20, y+20, fill="black", outline="black")
                elif game.board[row][col] == game.WHITE:
                    self.canvas.create_oval(x-20, y-20, x+20, y+20, fill="white", outline="black")
        
        # 标记合法移动位置
        valid_moves = game.get_valid_moves()
        for row, col in valid_moves:
            x = col * cell_size + cell_size // 2
            y = row * cell_size + cell_size // 2
            self.canvas.create_oval(x-5, y-5, x+5, y+5, fill="red")
        
        # 更新分数和当前玩家信息
        black_score, white_score = game.get_score()
        self.black_score_label.config(text=f"黑棋: {black_score}")
        self.white_score_label.config(text=f"白棋: {white_score}")
        
        player_text = "黑棋" if game.current_player == game.BLACK else "白棋"
        player_type = "人类" if not self.controller.is_current_player_ai() else "AI"
        self.current_player_label.config(text=f"当前: {player_text}({player_type})")
        
        # 更新AI决策时间
        current_player = self.controller.get_current_player()
        if isinstance(current_player, AIPlayer):
            self.timer_label.config(text=f"决策时间: {current_player.last_move_time:.2f}s")
        
    def canvas_click(self, event):
        """处理棋盘点击事件"""
        if self.controller.ai_thinking or self.controller.game.game_over:
            return
            
        # 如果当前玩家是人类，处理点击
        if not self.controller.is_current_player_ai():
            cell_size = 50
            col = event.x // cell_size
            row = event.y // cell_size
            
            if 0 <= row < self.controller.game.BOARD_SIZE and 0 <= col < self.controller.game.BOARD_SIZE:
                player = self.controller.get_current_player()
                if isinstance(player, HumanPlayer):
                    player.set_move(row, col)
    
    def on_ai_start(self):
        """AI开始思考时调用"""
        self.status_label.config(text="AI思考中...")
        
    def on_ai_end(self):
        """AI结束思考时调用"""
        self.status_label.config(text="")
        
    def ai_move(self):
        """手动触发AI移动"""
        if not self.controller.ai_thinking and self.controller.is_current_player_ai():
            self.controller.schedule_ai_move()
    
    def on_game_over(self):
        """游戏结束时调用"""
        game = self.controller.game
        black_score, white_score = game.get_score()
        
        if black_score > white_score:
            winner = "黑棋胜利!"
        elif white_score > black_score:
            winner = "白棋胜利!"
        else:
            winner = "平局!"
            
        messagebox.showinfo("游戏结束", f"游戏结束!\n黑棋: {black_score}\n白棋: {white_score}\n{winner}")
    
    def new_game(self):
        """开始新游戏"""
        self.controller.new_game()
        self.update_display()
        self.ai_vs_ai_btn.config(text="AI对战")
        self.status_label.config(text="")
        
    def toggle_ai_vs_ai(self):
        """切换AI对战模式"""
        if self.controller.ai_thinking:
            return
            
        # 切换AI对战模式
        new_mode = not self.controller.ai_vs_ai_mode
        self.controller.set_ai_vs_ai_mode(new_mode)
        
        # 更新按钮文本
        if new_mode:
            self.ai_vs_ai_btn.config(text="停止AI对战")
        else:
            self.ai_vs_ai_btn.config(text="AI对战")


def main():
    """主函数，创建游戏实例并启动"""
    root = tk.Tk()
    
    # 创建游戏实例
    game = OthelloGame()
    
    # 创建玩家实例
    black_player = HumanPlayer(game, game.BLACK)
    white_player = alphaBetaAI(game, game.WHITE)
    
    # 创建控制器
    controller = OthelloController(game, black_player, white_player)
    
    # 创建UI
    ui = OthelloUI(root, controller)
    
    root.mainloop()


if __name__ == "__main__":
    main()